/* 模型加载器：场景构建工具统一的模型加载
 *
 * 功能
 *   - 模型缓存：可以极大地优化内存占用，及导出的 GLTF 文件大小
 *   - 应用 ModelConfig 里的所有选项
 *   - 加载进度
 *
 * 计划包含功能
 *   [ ] 实例化静态模型
 */
// 第三方库
import * as THREE from "three";
import { keyBy, merge } from "lodash-es";
import EventEmitter from "emittery";
// 项目公用
import type { Dictionary } from "./utils/types";
import { object3d, layer } from "./utils";
import { UniformModelLoader } from "./UniformModelLoader";
// 当前页面
import type {
  ModelConfig,
  ModelListItem,
  StateAspect,
  SceneNodeData,
  SceneObjectStyleMaterials,
  SceneObjectStyleConfig,
} from "./types";
import {
  applyObjectStateSetting,
  alignObjectStateSetting,
} from "./utils/state";
import { type MaterialReplacer, materialReplacer } from "./MaterialReplacer";
// 当前模块
import {
  applyObjectMaterialsToObject,
  objectStyleToMaterial,
} from "./scene-config-utils";

import { toggleObjectVisible } from "./utils/object-op";


// 模型加载器
const uniformModelLoader = new UniformModelLoader();

// 事件类型
type ModelLoaderEvents = {
  "load-progress": { totalCount: number; loadedCount: number };
  "load-complete": {
    durationInSec: number;
    modelLoadingTime: { [modelName: string]: number };
  };
  "model-lost": string;
};

// 加载模型选项
type LoadModelOption = {
  positionOffset?: THREE.Vector3;
  objectStyle?: SceneObjectStyleConfig;
  objectStyleMaterials?: SceneObjectStyleMaterials;
  hasAncestorRenderModeRealistic?: boolean;
};

export class ModelLoader extends EventEmitter<ModelLoaderEvents> {
  // 静态属性
  static layerVisible = 1;
  // 主属性
  materialReplacer: MaterialReplacer;
  // 设置项
  baseUrl: string = "";

  /**
   * 模型加载器
   * @param userMaterialReplacer 要是用的材质替换器
   */
  constructor(userMaterialReplacer?: MaterialReplacer) {
    super();
    if (userMaterialReplacer) this.materialReplacer = userMaterialReplacer;
    else this.materialReplacer = materialReplacer;
  }

  // 设置模型列表
  modelListLength = 0;
  modelIdItemMap: Dictionary<ModelListItem> = {};
  modelIdStateMap: Dictionary<StateAspect[]> = {};
  setModelList(modelList: ModelListItem[]) {
    this.modelListLength = modelList.length;
    this.modelIdItemMap = keyBy(modelList, "id");
    const map: { [id: string]: StateAspect[] } = {};
    modelList.forEach((item) => (map[item.id] = item.stateAspects));
    this.modelIdStateMap = map;
  }

  // 加载一个模型库模型（ModelListItem）
  // 目前只用于：编辑器 -> 拖拽单个模型到场景内
  async loadModelLibModel(
    model: ModelListItem,
    onLoad?: (object: THREE.Object3D) => void
  ) {
    const modelUrl = "/api" + model.model;
    const modelName = model.name;
    const modelId = model.id;
    const object = await this.loadModelWithCache(modelUrl);
    object.name = modelName;
    const userData = object.userData as SceneNodeData;
    userData.modelId = modelId;
    userData.modelName = modelName;
    if (onLoad) onLoad(object);
  }

  /**
   * 加载多个场景内模型（ModelConfig）
   * @param models 模型Json列表
   * @param container 要加载到的 Object3D 容器
   * @param positionOffset （可选）加载时的整体偏移
   * @returns 加载场景的统计
   */
  async loadSceneModels(
    models: ModelConfig[],
    container: THREE.Object3D,
    userOptions?: LoadModelOption
  ) {
    const options = merge({}, userOptions);
    if (options.objectStyle && options.objectStyleMaterials == null) {
      options.objectStyleMaterials = objectStyleToMaterial(options.objectStyle);
    }
    if (this.modelListLength == 0) {
      console.error(
        "[ModelLoader] loadSceneModels: 需要先用 setModelList 方法设置模型列表之后才能加载场景"
      );
      return;
    }

    // 计算 totalCount
    this.totalCount = this.#getChildrenNodeCount(models);
    // console.log('totalCount', this.totalCount)

    // 加载进度逻辑
    this.loadedCount = 0;
    this.timeStart = Date.now();
    for await (const model of models) {
      await this.loadSceneModel(model, container, options);
    }
    this.timeEnd = Date.now();
    const duration = Math.floor(this.timeEnd - this.timeStart);
    // 与事件 load-complete 一样，延迟一帧
    setTimeout(() => {
      this.emit("load-complete", {
        durationInSec: duration / 1000,
        modelLoadingTime: this.modelLoadingTime,
      });
    });
  }

  // 获取树结构节点数量
  #getChildrenNodeCount(models: ModelConfig[]): number {
    let count = models.length;
    models.forEach((modelConfig) => {
      count += this.#getChildrenNodeCount(modelConfig.children);
    });
    return count;
  }

  // 加载进度
  totalCount = 0; // 场景树总节点数
  loadedCount = 0; // 已加载的场景树节点数量，组也算
  timeStart = 0; // 整个场景开始加载时间
  timeEnd = 0; // 整个场景完成加载时间
  onNodeLoadFinish() {
    this.loadedCount += 1;
    // 延迟1帧，是因为 Three.js 会延迟一帧才会把物体真正放进场景
    setTimeout(() => {
      this.emit("load-progress", {
        loadedCount: this.loadedCount,
        totalCount: this.totalCount,
      });
    });
  }

  // 场景内模型加载耗时统计
  modelLoadingTime: { [modelName: string]: number } = {};

  // 加载单个场景内模型（ModelConfig）
  async loadSceneModel(
    modelConfig: ModelConfig,
    container: THREE.Object3D,
    userOptions?: LoadModelOption
  ) {
    const options = merge({}, userOptions);
    const isGroup = modelConfig.modelId == "GROUP";
    let object!: THREE.Object3D;
    const modelItem = this.modelIdItemMap[modelConfig.modelId];
    if (isGroup) {
      object = new THREE.Group();
      this.onNodeLoadFinish();
    } else {
      // 加载当前模型
      if (modelItem == null) {
        this.emit("model-lost", modelConfig.name);
        this.onNodeLoadFinish();
        return;
      }
      // 加载耗时统计
      const modelUrl = this.baseUrl + modelItem.name + ".glb";
      const startTime = Date.now();
      object = await this.loadModelWithCache(modelUrl);
      const loadingTime = Date.now() - startTime;
      if (this.modelLoadingTime[modelConfig.name] == null) {
        this.modelLoadingTime[modelConfig.name] = loadingTime;
      } else if (loadingTime > this.modelLoadingTime[modelConfig.name]) {
        this.modelLoadingTime[modelConfig.name] = loadingTime;
      }
      this.onNodeLoadFinish();
    }
    // 设置 userData 属性
    const userData = object.userData as SceneNodeData;
    userData.modelId = isGroup ? "GROUP" : modelConfig.modelId;
    if (isGroup) userData.modelName = "";
    else userData.modelName = modelItem.name;
    userData.code = modelConfig.code;
    userData.renderMode = modelConfig.renderMode || "SCENE_OBJECT_STYLE";
    userData.specialAttribute = modelConfig.specialAttribute;
    userData.defaultStateSettings = modelConfig.defaultStateSettings || [];
    userData.isModel = !isGroup;
    userData.isGroup = isGroup;
    if (!isGroup)
      userData.materialReplacements = modelConfig.materialReplacements || [];
    // 设置 Object3D 属性
    object.name = modelConfig.name;
    layer.enable(object, ModelLoader.layerVisible);
    object.position.fromArray(modelConfig.position);
    if (options.positionOffset) object.position.add(options.positionOffset);
    object.rotation.fromArray(modelConfig.rotation);
    object.scale.fromArray(modelConfig.scale);
    const isVisible = modelConfig.visible == null ? true : modelConfig.visible;
    toggleObjectVisible(object, isVisible);
    container.add(object);

    // 应用默认状态设置
    if (!isGroup) {
      const stateAspects = this.modelIdStateMap[modelConfig.modelId];
      if (
        stateAspects.length > 0 &&
        stateAspects.length != userData.defaultStateSettings.length
      ) {
        alignObjectStateSetting(userData, stateAspects);
      }
      applyObjectStateSetting(
        object,
        stateAspects,
        userData.defaultStateSettings
      );
    }

    // 物体风格
    const isSceneRealistic =
      options.objectStyle == null || options.objectStyle.type == "REALISTIC"; // 1.场景设置为写实
    const isCurrentObjectRenderModeRealistic =
      userData.renderMode == "REALISTIC"; // 2.当前物体自身为写实
    const hasAncestorRenderModeRealistic =
      options.hasAncestorRenderModeRealistic; // 3.有某个上级节点为写实
    if (
      isSceneRealistic ||
      isCurrentObjectRenderModeRealistic ||
      hasAncestorRenderModeRealistic
    ) {
      // 材质替换
      if (modelConfig.materialReplacements?.length > 0) {
        modelConfig.materialReplacements.forEach((config) => {
          this.materialReplacer.replaceModelObject(
            object,
            config.meshIndex,
            config.materialId
          );
        });
      }
    } else {
      applyObjectMaterialsToObject(options.objectStyleMaterials!, object);
    }

    // 递归加载子模型
    options.hasAncestorRenderModeRealistic = isCurrentObjectRenderModeRealistic;
    for await (const childModelConfig of modelConfig.children) {
      await this.loadSceneModel(childModelConfig, object, options);
    }
  }

  // 为加载模型添加缓存
  modelCache: { [filePath: string]: THREE.Object3D } = {};
  loadModelWithCache(url: string) {
    if (this.modelCache[url] == null) {
      return new Promise<THREE.Object3D>((resolve, reject) => {
        uniformModelLoader.load(
          url,
          (obj) => {
            this.modelCache[url] = obj;
            resolve(object3d.cloneObjectWithMaterial(obj));
          },
          undefined,
          reject
        );
      });
    } else {
      return object3d.cloneObjectWithMaterial(this.modelCache[url]);
    }
  }

  // 清理缓存
  clearCache() {
    for (const member in this.modelCache) delete this.modelCache[member];
  }
}
